package zonefile

import (
	"context"
	"errors"
	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/Unixeno/TheMoon/common/constants"
	"strings"
)

type Zonefile struct {
	// 版本号，初始版本为1
	Version int `toml:"version"`

	// task规定了zonefile被部署时需要执行的任务
	// task有顺序，执行时会按照先后顺序，从第一个开始依次执行
	// task的基本格式为：命令 <参数1> [参数2]....
	//可选的命令有：
	//	run     container_name // 启动一个容器
	//	wait    container_name // 等待容器结束
	//	delete  container_name // 删除一个容器
	//	enable  network_name   // 激活一个网络
	//	disable network_name   // 禁用一个网络
	//	create  volume_name    // 创建一个卷
	//	remove  volume_name    // 删除一个卷
	// 仅在deploy阶段执行的task
	// 当所有任务执行完毕时，视为部署成功
	InitTask []string `toml:"init_task"`
	// 每次重启服务都会执行的task
	DeployTask []string `toml:"deploy_task"`
	// CleanTask 默认为空
	CleanTask []string `toml:"clean_task"`

	// 定义zone内的容器
	Container []*Container

	Network []*Network

	Volume []*Volume

	// init Task 的字节码
	initTask []*operation
	// deploy Task 的字节码
	deployTask []*operation
	// clean task 的字节码
	cleanTask []*operation

	// 是否校验过
	validated    bool
	containerMap map[string]*Container
	networkMap   map[string]*Network
	volumeMap    map[string]*Volume
}

type operation struct {
	action      CMD
	object      interface{}
	ignoreError bool
}

type Container struct {
	// 容器名
	Name string
	// 容器使用的镜像名称
	Image string
	// 容器的hostname
	Hostname string
	// 是否启用tty
	Tty        bool
	EntryPoint []string
	CMD        []string
	WorkingDir string
	// 设置容器内的dns（resolve.conf），为空时会使用节点的dns
	DNS []string
	// 传递给容器的环境变量
	Env []string
	// 用于生成容器内的hosts文件
	Hosts []HostEntry
	// 端口转发
	PortForwardConfig PortForward `toml:"port_forward_config"`

	Volumes map[string]string
	// 容器的网络，为空时容器只会拥有lo环回网络设备，无法访问外部互联网
	Interface []*Interface
}

// Source 是 Host port
// Target 是 Container port
// HostIP 默认 0.0.0.0
type PortForward struct {
	ContainerPort string `toml:"container_port"`
	HostPort      string `toml:"host_port"`
	HostIP        string `toml:"host_ip"`
}

type Interface struct {
	Name    string // 容器内veth的名称
	Network string // 需要桥接的网络
	Ip      string // 分配给容器的ip，为空时自动生成一个
	Mask    string // 子网掩码，为空时采用network的子网掩码
}

// 定义网络，即网桥
// 系统在启动后会创建一个默认网桥，zone0
type Network struct {
	// 网络名称

	// _ 是私有网络
	// 没有 _ 是公有网络
	// 私有网络的网桥实际名称命名规则为[ZoneID]_[Network.Name]
	// 有长度限制最多 8 位
	Name string

	// CIDR格式的网络地址，为空时网桥不设置地址
	Address string

	// 网桥桥接的其他网络设备
	BridgeTo []string `toml:"bridge_to"`
}

type Volume struct {
	// 卷名称 _ + 小写 是私有化的卷
	// _ + 大写 是私有持久化的卷
	// 没有 _ 表示 共享卷 默认持久化
	Name string
	// 卷位于宿主机的路径
	Source string
}

type HostEntry struct {
	Host    string
	Address string
}

const (
	maxNetworkNameLength = 8
)

func NewZoneFile(zonefile string) (*Zonefile, error) {
	z := &Zonefile{}
	_, err := toml.Decode(zonefile, z)
	if err != nil {
		return nil, err
	}
	return z, z.Validate()
}

func (z *Zonefile) Validate() error {
	// 检查必须字段是否设置
	if z.Version != 1 {
		return errors.New("unsupported version")
	}

	z.containerMap = make(map[string]*Container)
	z.networkMap = make(map[string]*Network)
	z.volumeMap = make(map[string]*Volume)
	for _, c := range z.Container {
		z.containerMap[c.Name] = c
		p1, p2 := c.PortForwardConfig.ContainerPort, c.PortForwardConfig.HostPort
		if (p1 == "" && p2 != "") || (p1 != "" && p2 == "") {
			return errors.New("invalid port forward pair")
		}
	}
	for _, n := range z.Network {
		if len(n.Name) > maxNetworkNameLength {
			return errors.New("network name '" + n.Name + "' length > 8")
		}
		z.networkMap[n.Name] = n
	}
	for _, v := range z.Volume {
		if v.Name[0] == '_' && len(v.Name) < 2 {
			return errors.New("invalid volume name '" + v.Name + "'")
		}
		z.volumeMap[v.Name] = v
	}

	// 验证卷
	for _, c := range z.Container {

		for name, _ := range c.Volumes {
			_, ok := z.volumeMap[name]
			if !ok {
				return errors.New("undefined volume '" + name + "'")
			}
		}
	}

	t1, err := z.parseStringTask(z.InitTask)
	if err != nil {
		return err
	}
	t2, err := z.parseStringTask(z.DeployTask)
	if err != nil {
		return err
	}
	t3, err := z.parseStringTask(z.CleanTask)
	if err != nil {
		return err
	}

	// 检查容器内的网络与卷
	for _, c := range z.Container {
		for name, _ := range c.Volumes {
			_, ok := z.volumeMap[name]
			if !ok {
				return fmt.Errorf("undefined volume %v", name)
			}
		}
		if c.Interface == nil || len(c.Interface) == 0 {
			continue
		}
		for _, i := range c.Interface {
			// 默认网络名可不写定义
			if i.Network == constants.DefaultNetworkName {
				continue
			}
			if _, ok := z.networkMap[i.Network]; !ok {
				return fmt.Errorf("undefined network %v", i.Network)
			}
		}

	}

	z.supplementInitTask(t1)
	z.deployTask = t2
	z.cleanTask = t3

	z.validated = true
	return nil
}

// 保证卷和网络的创建在容器之前
func (z *Zonefile) supplementInitTask(t []*operation) {
	for _, v := range z.Volume {
		z.initTask = append(z.initTask, &operation{
			action: CMDCreateVolume,
			object: v,
		})
	}

	for _, n := range z.Network {
		z.initTask = append(z.initTask, &operation{
			action: CMDEnableNetwork,
			object: n,
		})
	}

	z.initTask = append(z.initTask, t...)
}

func (z *Zonefile) parseStringTask(tasks []string) ([]*operation, error) {
	t := make([]*operation, 0, len(tasks))
	for _, taskString := range tasks {
		task := strings.Split(taskString, " ")
		if len(task) < 2 {
			return nil, errors.New("invalid task: " + taskString)
		}
		cmdString := task[0]
		ignoreError := false
		if cmdString[0] == '@' {
			ignoreError = true
			cmdString = cmdString[1:]
		}
		switch cmdString {
		case "run", "delete", "wait":
			c, ok := z.containerMap[task[1]]
			if !ok {
				return nil, errors.New("target container '" + task[1] + "' doesn't exist")
			}
			t = append(t, &operation{
				action:      stringToCMD(task[0]),
				object:      c,
				ignoreError: ignoreError,
			})

		case "enable", "disable":
			n, ok := z.networkMap[task[1]]
			if !ok {
				return nil, errors.New("target network '" + task[1] + "' doesn't exist")
			}
			t = append(t, &operation{
				action:      stringToCMD(task[0]),
				object:      n,
				ignoreError: ignoreError,
			})
		case "create", "remove":
			v, ok := z.volumeMap[task[1]]
			if !ok {
				return nil, errors.New("target volume '" + task[1] + "' doesn't exist")
			}
			t = append(t, &operation{
				action:      stringToCMD(task[0]),
				object:      v,
				ignoreError: ignoreError,
			})

		default:
			return nil, errors.New("invalid task command '" + task[0] + "'")
		}
	}

	return t, nil
}

type CMD int

const (
	CMDRunContainer CMD = iota
	CmdWaitContainer
	CMDDeleteContainer

	CMDEnableNetwork
	CMDDisableNetwork

	CMDCreateVolume
	CMDRemoveVolume
)

func (cmd CMD) String() string {
	switch cmd {
	case CMDRunContainer:
		return "runContainer"
	case CMDDeleteContainer:
		return "deleteContainer"
	case CMDEnableNetwork:
		return "enableNetwork"
	case CMDDisableNetwork:
		return "disableNetwork"
	case CMDCreateVolume:
		return "createVolume"
	case CMDRemoveVolume:
		return "removeVolume"
	default:
		return "unknownCMD"
	}
}

func stringToCMD(s string) CMD {
	switch s {
	case "wait":
		return CmdWaitContainer
	case "delete":
		return CMDDeleteContainer
	case "enable":
		return CMDEnableNetwork
	case "disable":
		return CMDDisableNetwork
	case "create":
		return CMDCreateVolume
	case "remove":
		return CMDRemoveVolume
	default:
		return CMDRunContainer
	}
}

var ErrContainerNotExist = errors.New("container doesn't exist")
var ErrNetworkNotExist = errors.New("network doesn't exist")
var ErrVolumeNotExist = errors.New("volume doesn't exist")
var ErrNetworkExist = errors.New("network exists")

type Executor interface {
	RunContainer(context.Context, *Container) error
	WaitContainer(context.Context, *Container) error
	DeleteContainer(context.Context, *Container) error
	EnableNetwork(context.Context, *Network) error
	DisableNetwork(context.Context, *Network) error
	CreateVolume(context.Context, *Volume) error
	RemoveVolume(context.Context, *Volume) error
}

func (z *Zonefile) ExecuteInitTask(ctx context.Context, executor Executor) error {
	if !z.validated {
		if err := z.Validate(); err != nil {
			return err
		}
	}
	return z.execute(ctx, z.initTask, executor)
}

func (z *Zonefile) ExecuteDeployTask(ctx context.Context, executor Executor) error {
	if !z.validated {
		if err := z.Validate(); err != nil {
			return err
		}
	}
	return z.execute(ctx, z.deployTask, executor)
}

func (z *Zonefile) ExecuteCleanTask(ctx context.Context, executor Executor) error {
	if !z.validated {
		if err := z.Validate(); err != nil {
			return err
		}
	}
	return z.execute(ctx, z.cleanTask, executor)
}

func (z *Zonefile) execute(ctx context.Context, tasks []*operation, executor Executor) error {
	for _, task := range tasks {
		var err error
		ignoreError := task.ignoreError
		switch task.action {
		case CMDRunContainer:
			c := task.object.(*Container)
			err = executor.RunContainer(ctx, c)
		case CMDDeleteContainer:
			c := task.object.(*Container)
			err = executor.DeleteContainer(ctx, c)
			if err == ErrContainerNotExist {
				err = nil
			}
		case CmdWaitContainer:
			c := task.object.(*Container)
			err = executor.WaitContainer(ctx, c)
		case CMDEnableNetwork:
			n := task.object.(*Network)
			err = executor.EnableNetwork(ctx, n)
			if err == ErrNetworkExist {
				err = nil
			}
		case CMDDisableNetwork:
			n := task.object.(*Network)
			err = executor.DisableNetwork(ctx, n)
			if err == ErrNetworkNotExist {
				err = nil
			}
		case CMDCreateVolume:
			v := task.object.(*Volume)
			err = executor.CreateVolume(ctx, v)
		case CMDRemoveVolume:
			v := task.object.(*Volume)
			err = executor.RemoveVolume(ctx, v)
			if err == ErrVolumeNotExist {
				err = nil
			}
		}
		if ignoreError {
			continue
		}
		if err != nil {
			return err
		}
	}

	return nil
}
